package com.ruoyi.common.crawler;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.google.common.collect.Maps;
import com.ruoyi.common.utils.TranslatorUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.compress.utils.Lists;
import org.apache.logging.log4j.util.Strings;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

import static com.ruoyi.common.crawler.WordTypeEnum.fixType;

@Slf4j
public enum WebSiteEnum implements WebSiteStrategy {

    Oxford("https://www.oxfordlearnersdictionaries.com") {
        @Override
        public boolean canProcess(String url) {
            return true;
        }

        @Override
        public Word process(String word) throws IOException {

            Word wordModel = new Word();
            wordModel.setWord(word);
            //查找一共支持多少含义
            List<WordItem> items = Lists.newArrayList();
            wordModel.setWordItems(items);
            Map<@Nullable WordTypeEnum, @Nullable List<String>> synonyms = null;
            try {
                synonyms = WebSiteEnum.WEBSTER.getSynonyms(word);
            } catch (Exception e) {
                log.error("单词多义查询失败", e);
            }

            int i = 1;
            for (; ; i++) {
                log.info("#### 单词多义查询 ###");
                String url = String.format("%s/definition/english/%s_%d", getUrl(), word, i);
                try {
                    Document               doc          = Jsoup.connect(url).get();
                    String                 usTouch      = getUsTouch(doc);
                    String                 ukTouch      = getUkTouch(doc);
                    String   usPhonetic = getUsPhonetic(doc);
                    String   ukPhonetic = getUkPhonetic(doc);
                    WordTypeEnum           wordType     = getWordType(doc);
                    List<WordItem.MeaningItem> meaningItems = getMeaningItems(doc);

                    WordItem wordItem = new WordItem();
                    wordItem.setWord(word);
                    wordItem.setWordTypeEnum(wordType);
                    wordItem.setUkTouch(ukTouch);
                    wordItem.setUsTouch(usTouch);
                    wordItem.setUsPhonetic(usPhonetic);
                    wordItem.setUkPhonetic(ukPhonetic);
                    wordItem.setMeaningItems(meaningItems);
                    if (MapUtils.isNotEmpty(synonyms)) {
                        wordItem.setSynonyms(synonyms.getOrDefault(wordType, Lists.newArrayList()));
                    }
                    items.add(wordItem);
                } catch (Exception e) {
                    log.error("第" + i + "次查询出错:" + word, e);
                    log.error("#### 多次查询出错:" + word, e);
                    break;
                }
            }

            return wordModel;
        }


        @Override
        public WordTypeEnum getWordType(Document doc) {
            String selector = "span.pos[htag='span'][hclass='pos']";
            Element spanElement = doc.select(selector).first();
            if (null == spanElement) {
                return WordTypeEnum.valueOf(Strings.EMPTY);
            }
            return WordTypeEnum.of(spanElement.text());
        }


        @Override
        public List<WordItem.MeaningItem> getMeaningItems(Document doc) {
            Element ul = doc.select("ol.sense_single,ol.senses_multiple").first();
            if (null == ul) {
                return Lists.newArrayList();
            }
            Elements               children   = ul.children();
            List<WordItem.MeaningItem> resultList = Lists.newArrayList();

            children.forEach(e -> {
                Element meaning = e.select("span.def[hclass='def'][htag='span']").first();
                if (null == meaning) {
                    return;
                }
                String text = meaning.text();
                log.info("meaning:{}", text);
                if (Strings.isNotEmpty(text)) {
                    WordItem.MeaningItem meaningItem = new WordItem.MeaningItem();
                    meaningItem.setMeaning(text);
                    meaningItem.setChineseMeaning(TranslatorUtil.translate(text));
                    Elements pictures = e.select("a.topic[title]");
                    List<String> pictureUrls = pictures.stream().map(x -> x.attr("href")).collect(Collectors.toList());
                    log.info("pictures:{}", pictureUrls);
                    meaningItem.setPictures(pictureUrls);
                    Elements examples = e.select("ul.examples>li");
                    List<String> exampleStrings = examples.stream().map(Element::text).limit(3).collect(Collectors.toList());
                    meaningItem.setExamples(exampleStrings);
                    List<String> chineseList = exampleStrings.stream().map(TranslatorUtil::translate).collect(Collectors.toList());
                    meaningItem.setExampleChineseTranslations(chineseList);
                    resultList.add(meaningItem);
                }
            });

            return resultList;
        }


        @Override
        public String getUsTouch(Document doc) {
            Element e = doc.select("div.sound.audio_play_button.pron-uk.icon-audio").first();
            if (e == null) {
                return Strings.EMPTY;
            }
            return e.attr("data-src-mp3");
        }

        @Override
        public String getUkTouch(Document doc) {
            Element e = doc.select("div.sound.audio_play_button.pron-us.icon-audio").first();
            if (e == null) {
                return Strings.EMPTY;
            }
            return e.attr("data-src-mp3");
        }

        @Override
        public String getPhonetic(Document doc) {
            return null;
        }

        @Override
        public String getUsPhonetic(Document doc) {
            Element e = doc.select("div.sound.audio_play_button.pron-us.icon-audio").first();
            if (e == null) {
                return Strings.EMPTY;
            }
            Element element = e.nextElementSibling();
            if (element == null) {
                return "";
            }
            return element.text();
        }
        @Override
        public String getUkPhonetic(Document doc) {
            Element e = doc.select("div.sound.audio_play_button.pron-us.icon-audio").first();
            if (e == null) {
                return Strings.EMPTY;
            }
            Element element = e.nextElementSibling();
            if (element == null) {
                return "";
            }
            return element.text();
        }
        @Override
        public Map<@Nullable WordTypeEnum, @Nullable List<String>> getSynonyms(String word) throws IOException {
            return null;
        }

        @Override
        public Map<@Nullable String, @Nullable List<String>> getSentenceMap(Document doc) throws IOException {

            return null;
        }

        @Override
        public void buildWordModel(Document doc, Map<@Nullable WordTypeEnum, @Nullable List<String>> synonymsMap, Map<String, List<String>> sentenceMap, Word wordModel) {

        }
    },
    WEBSTER("https://www.merriam-webster.com/") {
        @Override
        public WordTypeEnum getWordType(Document doc) {
            return null;
        }

        @Override
        public List<WordItem.MeaningItem> getMeaningItems(Document doc) {
            return null;
        }

        @Override
        public String getUkTouch(Document doc) {
            return null;
        }

        @Override
        public String getUsPhonetic(Document doc) {
            return getPhonetic(doc);
        }

        @Override
        public String getUkPhonetic(Document doc) {
            return null;
        }

        @Override
        public boolean canProcess(String url) {
            return true;
        }

        public Word process(String word) throws IOException {
            Connection connect = Jsoup.connect(this.getUrl() + "dictionary/" + word);
            for (int i = 0; i < 3; i++) {
                try {
                    Document document = connect.get();
                    String   usTouch  = getUsTouch(document);
                    String   phonetic = getPhonetic(document);

                    Word wordModel = new Word();
                    wordModel.setWord(word);
                    wordModel.setPhonetic(phonetic);
                    wordModel.setUsTouch(usTouch);
                    List<SubGroup> subGroups = Lists.newArrayList();
                    wordModel.setSubGroups(subGroups);

                    Map<@Nullable WordTypeEnum, @Nullable List<String>> synonymsMap = getSynonyms(word);
                    Map<String, List<String>>                     sentenceMap = getSentenceMap(document);
                    buildWordModel(document, synonymsMap, sentenceMap, wordModel);

                    return wordModel;
                } catch (IOException e) {
                    log.error("读取网页失败", e);
                }
            }

            return null;
        }

        public Map<@Nullable WordTypeEnum, @Nullable List<String>> getSynonyms(String word) throws IOException {

            Map<@Nullable WordTypeEnum, @Nullable List<String>> typeToObjetMap = Maps.newHashMap();
            Connection                                    connect        = Jsoup.connect(this.getUrl() + "thesaurus/" + word);
            Document                                      doc            = connect.get();
            Optional<Element>                             select         = doc.select(".important-blue-link").stream().findFirst();

            if (select.isPresent()) {
                Element element  = select.get();
                String  wordType = element.text();

                String s = fixType(wordType);
                WordTypeEnum wordTypeEnum = WordTypeEnum.of(s);

                Elements     elements = doc.select(".mw-grid-table-list ul li span span");
                List<String> synList  = Lists.newArrayList();

                for (Element e : elements) {
                    synList.add(e.text());
                }

                typeToObjetMap.put(wordTypeEnum, synList);

            }


            return typeToObjetMap;
        }

        public String getPhonetic(Document doc) {
            // 使用CSS选择器逐步定位到目标元素
            Optional<Element> spanElements = doc.select("span.prons-entries-list-inline.mb-1").stream().findFirst();

            // 如果找到了元素，获取其文本内容
            if (spanElements.isPresent()) {
                String text = spanElements.get().text();
                System.out.println("Text: " + text);
                return text;
            }

            return "";
        }

        public Map<String, List<String>> getSentenceMap(Document doc) {
            Map<@Nullable String, @Nullable List<String>> resultMap = Maps.newHashMap();

            Elements elements = doc.select("div#examples");
            for (Element element : elements) {
                String wordType = "default";

                Element mainSentenceDiv = element.select(".in-sentences-container").first();
                if (mainSentenceDiv != null) {
                    Element header = mainSentenceDiv.select("span.ex-header.function-label.content-section-sub-header").first();
                    if (header != null) {
                        wordType = header.text().trim().toLowerCase();
                    }
                }

                Elements sentences = element.select("span.d-block.thread-anchor-content.pb-1");

                List<String> sentenceTexts = sentences.stream().map(sentence -> sentence.text().trim()).collect(Collectors.toList());
                resultMap.put(wordType, sentenceTexts);
            }
            return resultMap;
        }


        public void buildWordModel(Document doc,
                                   Map<@Nullable WordTypeEnum, @Nullable List<String>> synonymsMap,
                                   Map<String, List<String>> sentenceMap,
                                   Word word) {
            Elements       elements  = doc.select("div.entry-word-section-container");
            List<SubGroup> groups    = Lists.newArrayList();
            boolean        isOnlyOne = elements.size() == 1;
            for (Element element : elements) {
                Element wordPart = element.select(".parts-of-speech").first();
                if (null == wordPart) {
                    continue;
                }
                String wordType = wordPart.text().trim();
                if (!WordTypeEnum.isValidType(wordType)) {
                    continue;
                }
                wordType = fixType(wordType);
                SubGroup     subGroup = new SubGroup();
                Elements     select   = element.select(".dtText");
                List<String> meanList = select.stream().map(e -> e.text().trim()).map(x -> x.split(": ")[1]).collect(Collectors.toList());
                subGroup.setMeans(meanList);
                subGroup.setType(WordTypeEnum.of(wordType));
                if (sentenceMap.containsKey(wordType) || (isOnlyOne && sentenceMap.containsKey("default"))) {
                    subGroup.setSentences(sentenceMap.getOrDefault(wordType, sentenceMap.get("default")));
                }
                if (synonymsMap.containsKey(wordType)) {
                    subGroup.setSynonyms(synonymsMap.get(wordType));
                }
                groups.add(subGroup);
            }
            word.setSubGroups(groups);
        }

        public String getUsTouch(Document doc) {
            // 选择包含JSON-LD数据的script标签
            // 选择所有的script标签

            String   result  = "";
            Elements scripts = doc.select("script");

            // 遍历所有的script标签
            for (Element script : scripts) {
                // 获取script标签的文本内容
                String scriptText = script.html();
                String mp3Url     = null;

                try {
                    JSONArray jsonArray = JSON.parseArray(scriptText);
                    for (int i = 0; i < jsonArray.size(); i++) {
                        JSONObject jsonObject = jsonArray.getJSONObject(i);
                        if (jsonObject.containsKey("@type") && jsonObject.getString("@type").equals("AudioObject")) {
                            mp3Url = jsonObject.getString("contentURL");
                            break;
                        }
                    }
                } catch (Exception e) {
                    log.error("解析JSON失败", e);
                }
                result = mp3Url;
                break;
            }
            return result;
        }
    },

    ;

    WebSiteEnum(String url) {
        this.url = url;
    }


    private final String url;


    public String getUrl() {
        return url;
    }


    public abstract WordTypeEnum getWordType(Document doc);

    public abstract List<WordItem.MeaningItem> getMeaningItems(Document doc);

    public abstract String getUkTouch(Document doc);

    public abstract String getUsPhonetic(Document doc);

    public abstract String getUkPhonetic(Document doc);
}

interface WebSiteStrategy {
    boolean canProcess(String url);

    Word process(String word) throws IOException;

    String getUsTouch(Document doc);

    String getPhonetic(Document doc);

    Map<@Nullable WordTypeEnum, @Nullable List<String>> getSynonyms(String word) throws IOException;

    Map<@Nullable String, @Nullable List<String>> getSentenceMap(Document doc) throws IOException;

    void buildWordModel(Document doc,
                        Map<@Nullable WordTypeEnum, @Nullable List<String>> synonymsMap,
                        Map<String, List<String>> sentenceMap,
                        Word word);
}
